<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      #map {
        position: relative;
        width: 300px;
        height: 300px;
        background-color: #999;
      }
    </style>
  </head>
  <body>
    <h1 id="grade">0</h1>
    <div id="map"></div>
    <script>
      class Map {
        constructor(el, rect = 10) {
          this.el = el;
          this.rect = rect;
          this.data = [];
          this.rows = Math.ceil(Map.getStyle(el, "height") / rect);
          this.cells = Math.ceil(Map.getStyle(el, "width") / rect);
          Map.setStyle(el, "height", this.rows * rect);
          Map.setStyle(el, "width", this.cells * rect);
        }
        static getStyle(el, attr) {
          return parseFloat(getComputedStyle(el)[attr]);
        }
        static setStyle(el, attr, val) {
          el.style[attr] = val + "px";
        }
        setData(newData) {
          this.data = this.data.concat(newData);
        }
        clearData() {
          this.data.length = 0;
        }
        include({ x, y }) {
          return !!this.data.some((item) => {
            item.x == x && item.y == y;
          });
        }
        render() {
          this.el.innerHTML = this.data
            .map((item) => {
              return `
                    <span style="width:${this.rect}px;height:${
                this.rect
              }px;position:absolute;left:${this.rect * item.x}px;top:${
                this.rect * item.y
              }px;background:${item.color};"></span>`;
            })
            .join("");
        }
      }

      class Food {
        constructor(map, colors = ["red", "blue", "yellow", "pink"]) {
          this.map = map;
          this.colors = colors;
          this.data = null;
          this.create();
        }
        create() {
          let x = Math.floor(Math.random() * this.map.cells);
          let y = Math.floor(Math.random() * this.map.rows);
          let color = this.colors[parseInt(Math.random() * this.colors.length)];
          this.data = {
            x,
            y,
            color,
          };
          if (this.map.include(this.data)) {
            this.create();
          }
          this.map.setData(this.data);
        }
      }

      class Snake {
        constructor(map, food) {
          this.map = map;
          this.data = [
            { x: 3, y: 5, color: "green" },
            { x: 2, y: 5, color: "#fff" },
            { x: 1, y: 5, color: "#fff" },
            { x: 0, y: 5, color: "#fff" },
          ];
          this.direction = "right";
          this.map.setData(this.data);
        }
        move() {
          let i = this.data.length - 1;
          this.lastData = {
            x: this.data[i].x,
            y: this.data[i].y,
            color: this.data[i].color,
          };
          // 让后边每一格走到前一个位置
          for (let i = this.data.length - 1; i > 0; i--) {
            this.data[i].x = this.data[i - 1].x;
            this.data[i].y = this.data[i - 1].y;
          }
          // 根据方向移动
          switch (this.direction) {
            case "left":
              this.data[0].x--;
              break;
            case "right":
              this.data[0].x++;
              break;
            case "top":
              this.data[0].y--;
              break;
            case "bottom":
              this.data[0].y++;
              break;
          }
        }
        changeDir(dir) {
          // 如果蛇本身左右移动只能修改成上下移动 如果蛇本身上下移动只能修改成左右移动
          if (
            ((this.direction === "left" || this.direction === "right") &&
              (dir === "left" || dir === "right")) ||
            ((this.direction === "top" || this.direction === "bottom") &&
              (dir === "top" || dir === "bottom"))
          ) {
            return false;
          } else {
            this.direction = dir;
            return true;
          }
        }
        eatFood() {
          this.data.push(this.lastData);
        }
      }

      class Game {
        constructor(el, rect, toControl = null, toGrade = 0, toOver = null) {
          this.map = new Map(el, rect);
          this.food = new Food(this.map);
          this.snake = new Snake(this.map, this.food);
          this.timer = 0;
          this.interval = 200;
          this.toControl = toControl;
          this.keyDown = this.keyDown.bind(this);
          this.grade = 0; //分数
          this.toGrade = toGrade;
          this.toOver = toOver;
          this.control();

          this.map.render();
        }
        // 开始游戏
        start() {
          this.move();
        }
        // 暂停游戏
        stop() {
          clearInterval(this.timer);
        }
        // 控制移动
        move() {
          this.stop();
          this.timer = setInterval(() => {
            this.snake.move();
            this.map.clearData();
            if (this.isEat()) {
              this.snake.eatFood();
              this.food.create();
              this.grade++;
              this.changeGrade(this.grade);
              this.interval *= 0.9;
              this.stop;
              this.start();
            }
            if (this.isOver()) {
              this.over();
              return;
            }
            this.map.setData(this.snake.data);
            this.map.setData(this.food.data);
            this.map.render();
          }, this.interval);
        }
        // 判断是否吃到食物
        isEat() {
          return (
            this.snake.data[0].x == this.food.data.x &&
            this.snake.data[0].y == this.food.data.y
          );
        }
        changeGrade(grade) {
          this.toGrade && this.toGrade(grade);
        }
        // 判断是否结束游戏
        isOver() {
          //判断蛇出图
          if (
            this.snake.data[0].x < 0 ||
            this.snake.data[0].x >= this.map.cells ||
            this.snake.data[0].y < 0 ||
            this.snake.data[0].y >= this.map.rows
          ) {
            return true;
          }
          // 判断蛇撞到自己
          for (let i = 1; i < this.snake.data; i++) {
            if (
              this.snake.data[0].x == this.snake.data[i].x &&
              this.snake.data[0].y == this.snake.data[i].y
            ) {
              return true;
            }
          }
          return false;
        }
        /**
         * 游戏结束
         * overStatus 1中途停止 2通关
         */
        over(overStatus) {
          this.toOver && this.toOver();
          console.log("gameOver");
          this.stop();
        }
        keyDown({ keyCode }) {
          switch (keyCode) {
            case 37:
              this.snake.changeDir("left");
              break;
            case 38:
              this.snake.changeDir("top");
              break;
            case 39:
              this.snake.changeDir("right");
              break;
            case 40:
              this.snake.changeDir("bottom");
              break;
          }
        }
        // 控制器
        control() {
          if (this.toControl) {
            this.toControl();
            return;
          }
          window.addEventListener("keydown", this.keyDown);
        }
      }

      {
        let map = document.querySelector("#map");
        let game = new Game(map, 10);
        let gradeView = document.querySelector("#grade");

        game.toGrade = function (grade) {
          gradeView.innerHTML = grade;
        };
        game.toOver = function () {
          alert("gameOver");
        };
        document.onclick = function () {
          game.start();
        };

        // document.onkeydown = function (event) {
        //   var e = event || window.event || arguments.callee.caller.arguments[0];
        //   if (e) {
        //     switch (e.keyCode) {
        //       case 37:
        //         game.snake.changeDir("left");
        //         break;
        //       case 38:
        //         game.snake.changeDir("top");
        //         break;
        //       case 39:
        //         game.snake.changeDir("right");
        //         break;
        //       case 40:
        //         game.snake.changeDir("bottom");
        //         break;
        //     }
        //   }
        // };
      }
    </script>
  </body>
</html>
